/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */
#include "ch1115.h"
#include <rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#include <string.h>
// For memcpy
/***********************************************************************************************
 * 变量名称：OLED_DisplayBuf
 * 功能描述：OLED显存数组
 * 注意事项：调用Update函数,才会将显存数组的数据发送到OLED硬件，进行显示
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/06/03        V1.0            WangJoy           创建
 **********************************************************************************************/

#define DBG_TAG              "ch1115"
#ifdef PKG_USING_SSD1306_DEBUG
#define DBG_LVL              DBG_LOG
#else
#define DBG_LVL              DBG_ERROR
#endif
#include <rtdbg.h>

uint8_t OLED_DisplayBuf[OLED_HEIGHT/8][OLED_WIDTH];
static struct rt_i2c_bus_device *i2c_bus;

#define RES_PIN_NUM 28
#define PEN_PIN_NUM 29
#define SCL_PIN_NUM 26
#define SDA_PIN_NUM 27

void ssd1306_pin_init(void)
{
    rt_pin_mode(RES_PIN_NUM, PIN_MODE_OUTPUT);
    rt_pin_mode(PEN_PIN_NUM, PIN_MODE_OUTPUT);
    // rt_pin_mode(SCL_PIN_NUM, PIN_MODE_OUTPUT);
    // rt_pin_mode(SDA_PIN_NUM, PIN_MODE_OUTPUT_OD);
}

#define ssd1306_res_pin(level) rt_pin_write(RES_PIN_NUM, level)
#define ssd1306_pen_pin(level) rt_pin_write(PEN_PIN_NUM, level)
#define ssd1306_scl_pin(level) rt_pin_write(SCL_PIN_NUM, level)
#define ssd1306_sda_pin(level) rt_pin_write(SDA_PIN_NUM, level)
#define ssd1306_sda_pin_read() rt_pin_read(SDA_PIN_NUM)
void OLED_Reset(void)
{
    /* for I2C - do nothing */
    ssd1306_pin_init();
    ssd1306_pen_pin(1);
    ssd1306_res_pin(0);
    rt_thread_mdelay(10);
    ssd1306_res_pin(1);
}

/***********************************************************************************************
 * 函数名称：OLED_IsInAngle
 * 功能描述：判断指定点是否在指定角度内部
 * 输入参数：X Y 指定点的坐标
 * 输入参数：StartAngle EndAngle 起始角度和终止角度，范围：-180~180
 * 输出参数：无
 * 返 回 值：指定点是否在指定多边形内部，1：在内部，0：不在内部
 * 注意事项：水平向右为0度，水平向左为180度或-180度，下方为正数，上方为负数，顺时针旋转
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/06/03        V1.0            WangJoy           创建
 **********************************************************************************************/
uint8_t OLED_IsInAngle(int16_t X, int16_t Y, int16_t StartAngle, int16_t EndAngle)
{
	int16_t PointAngle;
	PointAngle = atan2(Y, X) / 3.14 * 180;						 // 计算指定点的弧度，并转换为角度表示
	if (StartAngle < EndAngle)									 // 起始角度小于终止角度的情况
    {
		if (PointAngle >= StartAngle && PointAngle <= EndAngle)  // 如果指定角度在起始终止角度之间，则判定指定点在指定角度
			return 1;
    }
	else
    {														 // 起始角度大于于终止角度的情况
		if (PointAngle >= StartAngle || PointAngle <= EndAngle)	 // 如果指定角度大于起始角度或者小于终止角度，则判定指定点在指定角度
			return 1;
    }
	return 0;													 // 不满足以上条件，则判断判定指定点不在指定角度
}

/***********************************************************************************************
 * 函数名称：OLED_WriteByte
 * 功能描述：OLED写字节
 * 输入参数：Byte 要写入数据的起始地址, Count 要写入数据的数量
 * 输出参数：总线数据
 * 返 回 值：无
 * 注意事项：移植不同通信方式修改这个函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/08/29        V1.0             WangJoy           创建
 **********************************************************************************************/
void OLED_WriteByte(uint8_t *Data, uint8_t Count)
{
	for (int i=0; i<Count; i++)
    {
        uint8_t buf[2] = {SSD1306_CTRL_DATA, Data[i]};
        rt_i2c_master_send(i2c_bus, SSD1306_I2C_ADDR, RT_I2C_WR, buf, 2);
    }
    // HAL_SPI_Transmit(&hspi1, Data, Count, Max_time);  // HAL库硬件spi
}

/***********************************************************************************************
 * 函数名称：OLED_WriteCommand
 * 功能描述：OLED写命令
 * 输入参数：Command 要写入的命令值，范围：0x00~0xFF
 * 输出参数：总线数据
 * 返 回 值：无
 * 注意事项：保持D/C信号始终为高电平(数据)，可以更快速的传输数据
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_WriteCommand(uint8_t Command)
{
    // HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_RESET);  // 指令
    // OLED_WriteByte(&Command, 1);
    // HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_SET);    // 数据
    uint8_t buf[2] = {SSD1306_CTRL_CMD, Command};
    rt_i2c_master_send(i2c_bus, SSD1306_I2C_ADDR, RT_I2C_WR, buf, 2);
}

/***********************************************************************************************
 * 函数名称：OLED_WriteData
 * 功能描述：OLED写数据
 * 输入参数：Data 要写入数据的起始地址, Count 要写入数据的数量
 * 输出参数：总线数据
 * 返 回 值：无
 * 注意事项：D/C信号始终为高电平，提供数据速率
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
    OLED_WriteByte(Data, Count);
}

/***********************************************************************************************
 * 函数名称：OLED_Init
 * 功能描述：OLED初始化
 * 输入参数：无
 * 输出参数：总线信号
 * 返 回 值：无
 * 注意事项：无
 * 修改日期          版本号          修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/06/03        V1.0            王佳煜          创建
 **********************************************************************************************/
void OLED_Init(void)
{
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(PKG_USING_SSD1306_I2C_BUS_NAME);
    if (i2c_bus == RT_NULL)
    {
        LOG_E("can not find %s device", PKG_USING_SSD1306_I2C_BUS_NAME);
        return;
    }

    OLED_Reset();

    rt_thread_mdelay(50);  // 稳定一下，有的屏幕驱动芯片可不加

	// OLED_WriteCommand(0x30);/**set pump voltage 10V*/
	// OLED_WriteCommand(0x31);/**set pump voltage 7.4V*/
	// OLED_WriteCommand(0x32);/**set pump voltage 8.0V (power on)*/
	// // OLED_WriteCommand(0x33);/**set pump voltage 7.0V*/

	// OLED_WriteCommand(0x40);/** Set Display Start Line (40H-7FH)*/

	// OLED_WriteCommand(0x81);/** Set Contrast Control, (Double Bytes Command)*/
	// OLED_WriteCommand(0x88);/** Set Contrast Value 00H-FFH*/

	// OLED_WriteCommand(0x82);/**  IREF Resistor Set and Adjust ISEG by PAD: (Double Bytes Command) */
	// OLED_WriteCommand(0xA1);/**  Internal Resistor Set: (A1 – A0) */

	// OLED_WriteCommand(0XA0);/**  Set Segment Re-map: (A0H - A1H), A0:NORMAL, A1:REVERSE RAM<--->COLUMN*/

	// OLED_WriteCommand(0xA2);/**   Set SEG pads hardware configuration: (A2H – A3H) */

	// OLED_WriteCommand(0XA4);/** Set Entire Display OFF/ON: (A4H - A5H), A4:OFF A5:ON */

	// OLED_WriteCommand(0xA6);/** Set Normal/Reverse Display: (A6H - A7H), A6:Normal A7:Reverse */

	// OLED_WriteCommand(0xA8);/** Set Multiplex Ratio: (Double Bytes Command) */
	// OLED_WriteCommand(0x3F);/** Multiplex Ration Data Set: (00H - 3FH)*/

	// OLED_WriteCommand(0xAD);/**  DC-DC Control Mode Set: (ADH) */
	// OLED_WriteCommand(0x8A);/**  Set DC-DC OFF/ON: (Double Bytes Command) 8AH:0N, 8BH:OFF */

	// OLED_WriteCommand(0XAE);/** Alternatively turns the display on and off. AEH:OFF AFH:ON*/

	// OLED_WriteCommand(0XB0);/** Set Page Start Address for Page Addressing Mode: (B0H - B7H)*/

	// OLED_WriteCommand(0XC0);/** Set Common Output Scan Direction: (C0H - C8H), C0: COM0-COMN, C8:COMN-COM0*/

	// OLED_WriteCommand(0xD3);/** Set Display Offset: (Double Bytes Command) */
	// OLED_WriteCommand(0x00);/** Display Offset Data Set: (00H~3FH)*/

	// OLED_WriteCommand(0xD5);/**  Set Display Clock Divide Ratio/Oscillator Frequency: (Double Bytes Command) */
	// OLED_WriteCommand(0x50);/**  [A7:A4]:FREQUENCY [A1:A0]:DCLK*/

	// OLED_WriteCommand(0XD7);/** Set Adaptive Power Save: (D6H - D7H) */

	// OLED_WriteCommand(0XD6);/** Set Discharge/Pre-charge Period: (Double Bytes Command) */
	// OLED_WriteCommand(0x22);/**  [A7:A4]:Discharge Period Adjust: [A3:A0]:Pre-charge Period Adjust*/

	// OLED_WriteCommand(0xDB);/** Set VCOM Deselect Level: (Double Bytes Command) */
	// OLED_WriteCommand(0x15);/** VCOM Deselect Level Data Set: (00H - FFH)*/

	// OLED_WriteCommand(0xDC);/** Set row non-overlap/SEG Hiz Period: (Double Bytes Command). */
	// OLED_WriteCommand(0x01);/**  [A7:A4]:row non-overlap period: [A3:A0]:SEG Hiz Period*/

	// OLED_WriteCommand(0xE0);/**  Read-Modify-Write: (E0H) */
	// OLED_WriteCommand(0xEE);/**   End: (EEH) */
	// OLED_WriteCommand(0XE3);/** Non-Operation Command. */


	OLED_WriteCommand(ERMCH1115_DISPLAY_OFF);

	OLED_WriteCommand(ERMCH1115_SET_COLADD_LSB);
	OLED_WriteCommand(ERMCH1115_SET_COLADD_MSB);
	OLED_WriteCommand(ERMCH1115_SET_PAGEADD);//128x32, miss 4 page
	OLED_WriteCommand(ERMCH115_SET_DISPLAY_START_LINE);

	OLED_WriteCommand(ERMCH115_CONTRAST_CONTROL);
	OLED_WriteCommand(0xFF);

	OLED_WriteCommand(ERMCH1115_SEG_SET_REMAP);
	// OLED_WriteCommand(ERMCH1115_SEG_SET_REMAP);
	OLED_WriteCommand(ERMCH1115_DISPLAY_NORMAL);

	OLED_WriteCommand(ERMCH1115_MULTIPLEX_MODE_SET);
	OLED_WriteCommand(ERMCH1115_MULTIPLEX_DATA_SET );

	// OLED_WriteCommand(ERMCH1115_COMMON_SCAN_DIR);
	OLED_WriteCommand(ERMCH1115_COMMON_SCAN_DIR);

	OLED_WriteCommand(ERMCH1115_OFFSET_MODE_SET);
	OLED_WriteCommand(ERMCH1115_OFFSET_DATA_SET);

	OLED_WriteCommand(ERMCH1115_OSC_FREQ_MODE_SET);
	// OLED_WriteCommand(ERMCH1115_OSC_FREQ_DATA_SET);
	OLED_WriteCommand(0x80);

	OLED_WriteCommand(ERMCH1115_COM_LEVEL_MODE_SET);
	// OLED_WriteCommand(ERMCH1115_COM_LEVEL_DATA_SET);
	OLED_WriteCommand(0x05);

	OLED_WriteCommand(ERMCH1115_PRECHARGE_MODE_SET);
	// OLED_WriteCommand(ERMCH1115_PRECHARGE_DATA_SET);
	OLED_WriteCommand(0xf1);

	OLED_WriteCommand(0Xda);//set com pin configuartion
	OLED_WriteCommand(0X12);

	OLED_WriteCommand(0Xdb);//set Vcomh
	OLED_WriteCommand(0X30);

	OLED_WriteCommand(0X8d);//set charge pump
	OLED_WriteCommand(0X14);

	OLED_WriteCommand(ERMCH1115_DISPLAY_ON);

	// OLED_WriteCommand(ERMCH1115_IREF_REG);
	// OLED_WriteCommand(ERMCH1115_IREF_SET);

	// OLED_WriteCommand(ERMCH1115_SEG_SET_PADS);
	// OLED_WriteCommand(ERMCH1115_ENTIRE_DISPLAY_ON);



	// OLED_WriteCommand(ERMCH1115_SET_PUMP_REG);
	// OLED_WriteCommand(ERMCH115_SET_PUMP_SET);

	// OLED_WriteCommand(ERMCH1115_DC_MODE_SET);
	// OLED_WriteCommand(ERMCH1115_DC_ONOFF_SET);

	// OLED_WriteCommand(ERMCH1115_DISPLAY_ON);
}

/***********************************************************************************************
 * 函数名称：OLED_Update
 * 功能描述：将OLED显存数组更新到OLED屏幕
 * 输入参数：X 指定区域左上角的横坐标，范围：0~127, Y 指定区域左上角的纵坐标，范围：0~63
 * 输入参数：Width 指定区域的宽度，范围：0~128, Height 指定区域的高度，范围：0~64
 * 输出参数：总线信号
 * 返 回 值：无
 * 注意事项：所有显示函数，都只对显存读写
 * 修改日期          版本号          修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/06/03        V1.0            王佳煜          创建
 **********************************************************************************************/
void OLED_Update(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{

	/* 参数检查，保证指定区域不会超出屏幕范围 */
	if (X > (OLED_WIDTH-1) || Y > (OLED_HEIGHT-1))  {return;}
	if (X + Width > OLED_WIDTH)    {Width = OLED_WIDTH - X;}
	if (Y + Height > OLED_HEIGHT)    {Height = OLED_HEIGHT - Y;}

	for (uint8_t i = Y / 8; i < (Y + Height - 1) / 8 + 1; i ++)  // 遍历指定区域涉及的相关页
	{
        OLED_WriteCommand(ERMCH1115_SET_PAGEADD | (i));					         // 设置页位置
	    OLED_WriteCommand(ERMCH1115_SET_COLADD_MSB | ((X & 0xF0) >> 4));	         // 设置X位置高4位
	    OLED_WriteCommand(ERMCH1115_SET_COLADD_LSB | (X & 0x0F));			         // 设置X位置低4位
		OLED_WriteData(&OLED_DisplayBuf[i][X], Width);           // 将显存数组的数据写入到OLED
	}
}

/***********************************************************************************************
 * 函数名称：OLED_Clear
 * 功能描述：将OLED显存数组清零
 * 输入参数：X 指定区域左上角的横坐标，范围：0~127
 * 输入参数：Y 指定区域左上角的纵坐标，范围：0~63
 * 输入参数：Width 指定区域的宽度，范围：0~128
 * 输入参数：Height 指定区域的高度，范围：0~64
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：只对显存读写
 * 修改日期          版本号          修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/06/03        V1.0            王佳煜          创建
 **********************************************************************************************/
void OLED_Clear(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
	/* 参数检查，保证指定区域不会超出屏幕范围 */
    if (X > (OLED_WIDTH-1) || Y > (OLED_HEIGHT-1))  {return;}
	if (X + Width > OLED_WIDTH)    {Width = OLED_WIDTH-X;}
	if (Y + Height > OLED_HEIGHT)    {Height = OLED_HEIGHT-Y;}

	for(uint8_t j = Y; j < Y + Height; j ++)		          // 遍历指定页
		for(uint8_t i = X; i < X + Width; i ++)               // 遍历指定列
			OLED_DisplayBuf[j / 8][i] &= ~(0x01 << (j % 8));  // 将显存数组指定数据清零
}

 /***********************************************************************************************
 * 函数名称：OLED_ShowChar
 * 功能描述：OLED显示一个字符
 * 输入参数：X 指定字符左上角的横坐标，范围：0~127， Y 指定字符左上角的纵坐标，范围：0~63
 * 输入参数：Char 指定要显示的字符，范围：ASCII码可见字符， FontSize 指定字体大小
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：OLED_16x32的字符只有数字, 所以寻址范围 "-16"
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_ShowChar(uint8_t X, uint8_t Y, char Char, OLED_FontSize FontSize)
{
	if (FontSize == OLED_8X16)   OLED_ShowImage(X, Y, 8, 16, OLED_F8x16[Char - ' ']);
	if (FontSize == OLED_6X8)    OLED_ShowImage(X, Y, 6, 8,  OLED_F6x8[Char - ' ']);
    if (FontSize == OLED_16X32)  OLED_ShowImage(X, Y, 16, 32,  OLED_F16x32[Char - ' ' - 16]);
}

/***********************************************************************************************
 * 函数名称：OLED_ShowString
 * 功能描述：OLED显示字符串
 * 输入参数：X 指定字符串左上角的横坐标，范围：0~127， Y 指定字符串左上角的纵坐标，范围：0~63
 * 输入参数：String 指定要显示的字符串，范围：ASCII码可见字符组成的字符串， FontSize 指定字体大小
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：只对显存读写
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_ShowString(uint8_t X, uint8_t Y, char *String, OLED_FontSize FontSize)
{
	for(uint8_t i = 0; String[i] != '\0'; i++)  // 遍历字符串的每个字符
		OLED_ShowChar(X + i * FontSize, Y, String[i], FontSize);
}

/***********************************************************************************************
 * 函数名称：OLED_ShowNum
 * 功能描述：OLED显示数字（十进制，正整数）
 * 输入参数：X 指定数字左上角的横坐标，范围：0~127， Y 指定数字左上角的纵坐标，范围：0~63
 * 输入参数：Number 指定要显示的数字，范围：0~4294967295， FontSize 指定字体大小
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：只对显存读写
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_ShowNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, OLED_FontSize FontSize)
{
	for(uint8_t i = 0; i < Length; i++)  // 遍历数字的每一位
		OLED_ShowChar(X+i*FontSize, Y, Number/(int)pow(10, Length-i-1)%10+'0', FontSize);
    /* Number/(int)pow(10, Length-i-1)%10 十进制提取数字的每一位 */
}

/***********************************************************************************************
 * 函数名称：OLED_ShowFullNum
 * 功能描述：OLED完整数字（十进制）
 * 输入参数：X 指定数字左上角的横坐标，范围：0~127， Y 指定数字左上角的纵坐标，范围：0~63
 * 输入参数：Number 指定要显示的数字，范围：-4294967295.0~4294967295.0
 * 输入参数：Int 指定数字的整数位长度，范围：0~10， Fra 指定数字的小数位长度，范围：0~9，小数四舍五入显示
 * 输入参数：IsFilled 是否显示符号， 范围：OLED_UNFILLED-不显示 OLED_FILLED-显示
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：只对显存读写
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_ShowFullNum(uint8_t X, uint8_t Y, double Number, uint8_t Int, uint8_t Fra, OLED_IsFilled IsFilled, OLED_FontSize FontSize)
{
	uint32_t PowNum, IntNum, FraNum;

    if (IsFilled == OLED_FILLED)        // 显示符号
        Number >= 0 ? OLED_ShowChar(X, Y, '+', FontSize) : OLED_ShowChar(X, Y, '-', FontSize);

    if (Number < 0)  Number = -Number;  // 若为负数，则将其转为正数，方便后续处理
	IntNum = Number;					// 直接赋值给整型变量，提取整数
	Number -= IntNum;					// 将Number的整数减掉，防止之后将小数乘到整数时因数过大造成错误
	PowNum = (int)pow(10, Fra);		    // 根据指定小数的位数，确定乘数
	FraNum = round(Number * PowNum);	// 将小数乘到整数，同时四舍五入，避免显示误差
	IntNum += FraNum / PowNum;			// 若四舍五入造成了进位，则需要再加给整数

	OLED_ShowNum(X + FontSize, Y, IntNum, Int, FontSize);              // 显示整数
	OLED_ShowChar(X + (Int + 1) * FontSize, Y, '.', FontSize);         // 显示小数点
	OLED_ShowNum(X + (Int + 2) * FontSize, Y, FraNum, Fra, FontSize);  // 显示小数
}

/***********************************************************************************************
 * 函数名称：OLED_ShowChinese
 * 功能描述：OLED显示汉字串
 * 输入参数：X 指定汉字串左上角的横坐标，范围：0~127， Y 指定汉字串左上角的纵坐标，范围：0~63
 * 输入参数：Chinese 指定要显示的汉字串，范围：必须全部为汉字或者全角字符，不要加入任何半角字符
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：汉字需要在OLED_CF16x16定义,未找到指定汉字时，会显示默认图形（一个方框，内部一个问号）
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_ShowChinese(uint8_t X, uint8_t Y, char *Chinese)
{
    // uint8_t pChinese = 0;
	// char SingleChinese[OLED_CHN_CHAR_WIDTH + 1] = {0};

	// for (uint8_t i = 0; Chinese[i] != '\0'; i ++)  // 遍历汉字串
	// {
	// 	SingleChinese[pChinese] = Chinese[i];	   // 提取汉字串数据到单个汉字数组
	// 	pChinese ++;							   // 计次自增

	// 	if (pChinese >= OLED_CHN_CHAR_WIDTH)       // 提取次数到达OLED_CHN_CHAR_WIDTH，即提取到了一个完整的汉字
	// 	{
	// 		pChinese = 0;  // 计次归零
	// 		for (uint8_t pIndex = 0; strcmp(OLED_CF16x16[pIndex].Index, "") != 0; pIndex ++)  // 遍历字模库，寻找匹配汉字
	// 			if (strcmp(OLED_CF16x16[pIndex].Index, SingleChinese) == 0)                   // 找到匹配的汉字
    //             {
    //                 OLED_ShowImage(X + ((i + 1) / OLED_CHN_CHAR_WIDTH - 1) * 16, Y, 16, 16, OLED_CF16x16[pIndex].Data);
	// 				break;  // 跳出循环，此时pIndex的值为指定汉字的索引
    //             }
	// 	}
	// }
}

/***********************************************************************************************
 * 函数名称：OLED_ShowImage
 * 功能描述：OLED显示图像
 * 输入参数：X 指定图像左上角的横坐标，范围：0~127， Y 指定图像左上角的纵坐标，范围：0~63
 * 输入参数：Width 指定图像的宽度，范围：0~128, Height 指定图像的高度，范围：0~64, Image 指定要显示的图像
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_ShowImage(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image)
{
    /* 参数检查，保证指定区域不会超出屏幕范围 */
	if (X > (OLED_WIDTH-1) || Y > (OLED_HEIGHT-1))  {return;}

	OLED_Clear(X, Y, Width, Height);                     // 将图像所在区域清空

	for (uint8_t j = 0; j < (Height - 1) / 8 + 1; j ++)  // 遍历指定图像涉及的相关页
	{
		for (uint8_t i = 0; i < Width; i ++)             // 遍历指定图像涉及的相关列
		{
			if (X + i > 127 || Y / 8 + j > 7)  {break;}
			OLED_DisplayBuf[Y / 8 + j][X + i] |= Image[j * Width + i] << (Y % 8);          // 显示图像在当前页的内容
			if (Y / 8 + j + 1 > 7)             {continue;}                                 // 超出边界，则跳过显示
			OLED_DisplayBuf[Y / 8 + j + 1][X + i] |= Image[j * Width + i] >> (8 - Y % 8);  // 显示图像在下一页的内容
		}
	}
}

/***********************************************************************************************
 * 函数名称：OLED_DrawPoint
 * 功能描述：OLED在指定位置画一个点
 * 输入参数：X 横坐标，范围：0~127， Y 纵坐标，范围：0~63
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_DrawPoint(uint8_t X, uint8_t Y)
{
    if (X > 127 || Y > 63)  {return;}  // 参数检查，保证指定区域不会超出屏幕范围
	OLED_DisplayBuf[Y / 8][X] |= 0x01 << (Y % 8);
}

/***********************************************************************************************
 * 函数名称：OLED_DrawLine
 * 功能描述：OLED在指定位置画一个点
 * 输入参数：X0 指定一个端点的横坐标，范围：0~127， Y0 指定一个端点的纵坐标，范围：0~63
 * 输入参数：X1 指定另一个端点的横坐标，范围：0~127， Y1 指定另一个端点的纵坐标，范围：0~63
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_DrawLine(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1)
{
	int16_t x, y, dx, dy, d, incrE, incrNE, temp;
	int16_t x0 = X0, y0 = Y0, x1 = X1, y1 = Y1;
	uint8_t yflag = 0, xyflag = 0;

	if (y0 == y1)		// 横线单独处理
	{
		/* 0号点X坐标大于1号点X坐标，则交换两点X坐标 */
		if (x0 > x1) {temp = x0; x0 = x1; x1 = temp;}
		for (x = x0; x <= x1; x ++)  // 遍历X坐标
			OLED_DrawPoint(x, y0);	 // 依次画点
	}
	else if (x0 == x1)	// 竖线单独处理
	{
		/* 0号点Y坐标大于1号点Y坐标，则交换两点Y坐标 */
		if (y0 > y1) {temp = y0; y0 = y1; y1 = temp;}
		for (y = y0; y <= y1; y ++)  // 遍历Y坐标
			OLED_DrawPoint(x0, y);	 // 依次画点
	}
	else				// 斜线
	{
		if (x0 > x1)  {temp = x0; x0 = x1; x1 = temp; temp = y0; y0 = y1; y1 = temp;}
		if (y0 > y1)  {y0 = -y0; y1 = -y1; yflag = 1;}
		if (y1 - y0 > x1 - x0)  {temp = x0; x0 = y0; y0 = temp; temp = x1; x1 = y1; y1 = temp; xyflag = 1;}

		/* 以下为Bresenham算法画直线 */
		/* 算法要求，画线方向必须为第一象限0~45度范围 */
		dx = x1 - x0; dy = y1 - y0;
		incrE = 2 * dy; incrNE = 2 * (dy - dx);
		d = 2 * dy - dx; x = x0; y = y0;

		/* 画起始点，同时判断标志位，将坐标换回来 */
		if (yflag && xyflag)  {OLED_DrawPoint(y, -x);}
		else if (yflag)		  {OLED_DrawPoint(x, -y);}
		else if (xyflag)	  {OLED_DrawPoint(y,  x);}
		else				  {OLED_DrawPoint(x,  y);}

		while (x++ < x1)  // 遍历X轴的每个点
		{
			if (d < 0)    // 下一个点在当前点东方
				d += incrE;
			else          // 下一个点在当前点东北方
			{
				y ++; d += incrNE;
			}
			/* 画每一个点，同时判断标志位，将坐标换回来 */
			if (yflag && xyflag)  {OLED_DrawPoint(y, -x);}
			else if (yflag)		  {OLED_DrawPoint(x, -y);}
			else if (xyflag)	  {OLED_DrawPoint(y,  x);}
			else				  {OLED_DrawPoint(x,  y);}
		}
	}
}

/***********************************************************************************************
 * 函数名称：OLED_DrawRectangle
 * 功能描述：OLED画矩形
 * 输入参数：X 指定矩形左上角的横坐标，范围：0~127， Y 指定矩形左上角的纵坐标，范围：0~63
 * 输入参数：Width 指定矩形的宽度，范围：1~128， Height 指定矩形的高度，范围：1~64， IsFilled 指定矩形是否填充
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_DrawRectangle(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, OLED_IsFilled IsFilled)
{
    /* 参数检查，保证指定区域不会超出屏幕范围 */
    if (X > (OLED_WIDTH-1) || Y > (OLED_HEIGHT-1))  {return;}
	if (X + Width > OLED_WIDTH)    {Width = OLED_WIDTH - X;}
	if (Y + Height > OLED_HEIGHT)    {Height = OLED_HEIGHT - Y;}

	if (IsFilled == OLED_UNFILLED)		//指定矩形不填充
	{
        OLED_DrawLine(X        , Y         , X+Width-1, Y         );  // 上边
        OLED_DrawLine(X        , Y         , X        , Y+Height-1);  // 左边
        OLED_DrawLine(X+Width-1, Y         , X+Width-1, Y+Height-1);  // 右边
        OLED_DrawLine(X        , Y+Height-1, X+Width-1, Y+Height-1);  // 下边
    }

	if (IsFilled == OLED_FILLED)  // 指定矩形填充
        for (uint8_t i = Y; i < Y + Height; i++)               // 遍历指定页
            OLED_DrawLine(X, i, X + Width - 1, i);             // 填充

    if (IsFilled == OLED_COUNTER)  // 指定矩形取反
        for (uint8_t j = Y; j < Y + Height; j ++)              // 遍历指定页
            for (uint8_t i = X; i < X + Width; i ++)           // 遍历指定列
                OLED_DisplayBuf[j / 8][i] ^= 0x01 << (j % 8);  // 将显存数组指定数据取反
}

/***********************************************************************************************
 * 函数名称：OLED_DrawArc
 * 功能描述：OLED画圆弧
 * 输入参数：X 指定圆弧的圆心横坐标，范围：0~127， Y 指定圆弧的圆心纵坐标，范围：0~63, IsFilled 指定圆弧是否填充，填充后为扇形
 * 输入参数：StartAngle 起始角度，范围：-180~180, 水平向右为0度，水平向左为180度或-180度，下方为正数，上方为负数，顺时针旋转
 * 输入参数：EndAngle 终止角度，范围：-180~180, 水平向右为0度，水平向左为180度或-180度，下方为正数，上方为负数，顺时针旋转
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_DrawArc(uint8_t X, uint8_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, OLED_IsFilled IsFilled)
{
	int16_t x, y, d;
	/* 此函数借用Bresenham算法画圆的方法 */
	d = 1 - Radius;
	x = 0;
	y = Radius;

	/* 在画圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理 */
	if (OLED_IsInAngle( x,  y, StartAngle, EndAngle))  {OLED_DrawPoint(X + x, Y + y);}  // 1
	if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle))  {OLED_DrawPoint(X - x, Y - y);}  // 4
	if (OLED_IsInAngle( y,  x, StartAngle, EndAngle))  {OLED_DrawPoint(X + y, Y + x);}  // 2
	if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle))  {OLED_DrawPoint(X - y, Y - x);}  // 3

	if (IsFilled == OLED_FILLED)  // 指定圆弧填充
		for (uint8_t j = -y; j < y; j ++)  // 遍历起始点Y坐标
			if (OLED_IsInAngle(0, j, StartAngle, EndAngle))  // 判断指定点是否在指定角度内
                OLED_DrawPoint(X, Y + j);

	while (x++ < y)  // 遍历X轴的每个点
	{
		if (d < 0)   // 下一个点在当前点东方
			d += 2 * x + 1;
		else         // 下一个点在当前点东南方
		{
			y --;
			d += 2 * (x - y) + 1;
		}

		/* 在画圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理 */
		if (OLED_IsInAngle( x,  y, StartAngle, EndAngle))  {OLED_DrawPoint(X + x, Y + y);}
		if (OLED_IsInAngle( y,  x, StartAngle, EndAngle))  {OLED_DrawPoint(X + y, Y + x);}
		if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle))  {OLED_DrawPoint(X - x, Y - y);}
		if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle))  {OLED_DrawPoint(X - y, Y - x);}
		if (OLED_IsInAngle( x, -y, StartAngle, EndAngle))  {OLED_DrawPoint(X + x, Y - y);}
		if (OLED_IsInAngle( y, -x, StartAngle, EndAngle))  {OLED_DrawPoint(X + y, Y - x);}
		if (OLED_IsInAngle(-x,  y, StartAngle, EndAngle))  {OLED_DrawPoint(X - x, Y + y);}
		if (OLED_IsInAngle(-y,  x, StartAngle, EndAngle))  {OLED_DrawPoint(X - y, Y + x);}

		if (IsFilled == OLED_FILLED)	// 指定圆弧填充
		{
			for (uint8_t j = -y; j < y; j ++)  // 遍历中间部分
			{
				/* 在填充圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理 */
				if (OLED_IsInAngle( x, j, StartAngle, EndAngle))  {OLED_DrawPoint(X + x, Y + j);}
				if (OLED_IsInAngle(-x, j, StartAngle, EndAngle))  {OLED_DrawPoint(X - x, Y + j);}
			}
			for (uint8_t j = -x; j < x; j ++)  // 遍历两侧部分
			{
				/* 在填充圆的每个点时，判断指定点是否在指定角度内，在，则画点，不在，则不做处理 */
				if (OLED_IsInAngle(-y, j, StartAngle, EndAngle))  {OLED_DrawPoint(X - y, Y + j);}
				if (OLED_IsInAngle( y, j, StartAngle, EndAngle))  {OLED_DrawPoint(X + y, Y + j);}
			}
		}
	}
}

/***********************************************************************************************
 * 函数名称：OLED_DrawCircle
 * 功能描述：OLED画圆
 * 输入参数：X 指定圆的圆心横坐标，范围：0~127， Y 指定圆的圆心纵坐标，范围：0~63
 * 输入参数：Radius 指定圆的半径，范围：0~255， IsFilled 指定圆是否填充
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：调用此函数后，要想真正地呈现在屏幕上，还需调用更新函数
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/26        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_DrawCircle(uint8_t X, uint8_t Y, uint8_t Radius, OLED_IsFilled IsFilled)
{
    OLED_DrawArc(X, Y, Radius,    0,  90, IsFilled);  // 1
    OLED_DrawArc(X, Y, Radius,   90, 180, IsFilled);  // 2
    OLED_DrawArc(X, Y, Radius, -180, -90, IsFilled);  // 3
    OLED_DrawArc(X, Y, Radius,  -90,   0, IsFilled);  // 4
}

 /***********************************************************************************************
 * 函数名称：OLED_Roundrectangle
 * 功能描述：OLED画圆角矩形
 * 输入参数：X 指定圆的圆心横坐标，范围：0~127，Y 指定圆的圆心纵坐标，范围：0~63
 * 输入参数：Width 指定矩形的宽度，范围：0~128, Height 指定矩形的高度，范围：0~64
 * 输入参数：Radius 指定圆弧的半径，范围：0~255, IsFilled 指定矩形是否填充
 * 输出参数：显存数据
 * 返 回 值：无
 * 注意事项：圆角使用直线填充，圆弧填充极大降低运行速度，圆角在3以下时，足以视成圆弧
 * 修改日期          版本号            修改人          修改内容
 * --------------------------------------------------------------------------------------------
 * 2021/07/27        V1.0            WangJoy           创建
 **********************************************************************************************/
void OLED_Roundrectangle(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, uint8_t Radius, OLED_IsFilled IsFilled)
{
    if (IsFilled == OLED_UNFILLED)
    {
        OLED_DrawLine(X+Radius        , Y                , X+Width-Radius-1, Y                );  // 上边
        OLED_DrawLine(X+Radius        , Y+Height-1       , X+Width-Radius-1, Y+Height-1       );  // 下边
        OLED_DrawLine(X               , Y+Radius         , X               , Y+Height-Radius-1);  // 左边
        OLED_DrawLine(X+Width-1       , Y+Radius         , X+Width-1       , Y+Height-Radius-1);  // 右边
        OLED_DrawLine(X+Radius        , Y                , X               , Y+Radius         );  // 左上角
        OLED_DrawLine(X+Width-Radius-1, Y                , X+Width-1       , Y+Radius         );  // 右上角
        OLED_DrawLine(X               , Y+Height-Radius-1, X+Radius        , Y+Height-1       );  // 左下角
        OLED_DrawLine(X+Width-1       , Y+Height-Radius-1, X+Width-Radius-1, Y+Height-1       );  // 右下角
    }
    if (IsFilled == OLED_FILLED || IsFilled == OLED_COUNTER)
    {
        OLED_DrawRectangle(X, Y, Width, Height, IsFilled);  // 整块区域
        for (uint8_t n = 0; n < Radius; n++)
        {
            OLED_Clear(X               , Y+n              , Radius-n, 1);  // 左上角
            OLED_Clear(X+Width-Radius+n, Y+n              , Radius-n, 1);  // 右上角
            OLED_Clear(X               , Y+Height-Radius+n, n+1     , 1);  // 左下角
            OLED_Clear(X+Width-n-1     , Y+Height-Radius+n, n+1     , 1);  // 右下角
        }
    }
}
